Skip to content

feat(clerk-js,ui): Add OAuth transport support for external auth flows#8831

Open
wobsoriano wants to merge 5 commits into
mainfrom
rob/USER-5494-poc
Open

feat(clerk-js,ui): Add OAuth transport support for external auth flows#8831
wobsoriano wants to merge 5 commits into
mainfrom
rob/USER-5494-poc

Conversation

@wobsoriano

@wobsoriano wobsoriano commented Jun 11, 2026

Copy link
Copy Markdown
Member

Description

Adds an internal OAuth transport seam that lets Clerk UI auth flows hand off OAuth to an external runtime, such as Electron opening the system browser, while reusing the existing Clerk redirect/popup initiation logic.

This is intended to support @clerk/electron without making the core UI components directly know about Electron.

Electron cannot safely use the normal embedded browser redirect behavior for OAuth/SSO. The Electron SDK needs a way to open OAuth in the system browser and return to the app through a deep link, without duplicating Clerk’s OAuth initiation logic in UI code.

This keeps the runtime-specific behavior behind one internal transport primitive instead of adding Electron-specific branching throughout the components.

What changed

  • Adds __internal_oauthTransport as an internal Clerk option.
  • Reuses the existing authenticateWithRedirectOrPopup flow and captures the generated OAuth verification URL instead of navigating in-browser.
  • Adds transport support for:
    • SignIn social OAuth buttons
    • SignUp social OAuth buttons
    • UserProfile connected account linking
  • Generalizes the resource callback handling so the external transport can resume through the existing redirect callback state machine.
  • Keeps enterprise SSO and enterprise account linking out of scope for this first pass.

Usage

The Electron SDK can register an internal OAuth transport on ClerkProvider. The UI components still use the normal Clerk APIs, but OAuth handoff happens through Electron IPC instead of browser navigation.

import { ClerkProvider } from '@clerk/react';

export function App() {
  return (
    <ClerkProvider
      publishableKey={PUBLISHABLE_KEY}
      __internal_oauthTransport={{
        getRedirectUrl: () => window.__clerk_internal_electron.getOAuthRedirectUrl(),
        open: url => window.__clerk_internal_electron.openOAuthUrl(url.href),
      }}
    >
      <SignIn />
    </ClerkProvider>
  );
}

The Electron preload/main process owns window.__clerk_internal_electron: it opens the OAuth URL in the system browser, waits for the deep-link callback, then resolves with the callback URL so Clerk can resume the existing redirect callback flow.

Checklist

  • pnpm test runs as expected.
  • pnpm build runs as expected.
  • (If applicable) JSDoc comments have been added or updated for any package exports
  • (If applicable) Documentation has been updated

Type of change

  • 🐛 Bug fix
  • 🌟 New feature
  • 🔨 Breaking change
  • 📖 Refactoring / dependency upgrade / documentation
  • other:

Summary by CodeRabbit

  • New Features

    • Adds an internal OAuth transport to support OAuth/SSO via an external system browser (for native/desktop apps).
    • Exposes an optional navigation hook to customize post-auth navigation when completing OAuth callbacks.
    • UI components adapt popup vs redirect behavior to leverage the transport and pass callback parameters.
  • Bug Fixes / Improvements

    • More robust handling of OAuth callback flows and error/idle states in social buttons and account connect flows.
  • Tests

    • Added extensive tests covering OAuth transport, redirect flows, and UI behaviors.

@vercel

vercel Bot commented Jun 11, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
clerk-js-sandbox Ready Ready Preview, Comment Jun 12, 2026 3:12am
swingset Ready Ready Preview, Comment Jun 12, 2026 3:12am

Request Review

@changeset-bot

changeset-bot Bot commented Jun 11, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 871776e

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 21 packages
Name Type
@clerk/shared Minor
@clerk/clerk-js Minor
@clerk/ui Minor
@clerk/astro Patch
@clerk/backend Patch
@clerk/chrome-extension Patch
@clerk/expo-passkeys Patch
@clerk/expo Patch
@clerk/express Patch
@clerk/fastify Patch
@clerk/hono Patch
@clerk/localizations Patch
@clerk/msw Patch
@clerk/nextjs Patch
@clerk/nuxt Patch
@clerk/react-router Patch
@clerk/react Patch
@clerk/tanstack-react-start Patch
@clerk/testing Patch
@clerk/vue Patch
@clerk/swingset Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Repository UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: 1a7a5e9a-96cc-4899-9ca8-0a42fed8bd09

📥 Commits

Reviewing files that changed from the base of the PR and between 78d0941 and 09fcb3b.

📒 Files selected for processing (1)
  • packages/ui/bundlewatch.config.json
✅ Files skipped from review due to trivial changes (1)
  • packages/ui/bundlewatch.config.json

📝 Walkthrough

Walkthrough

Adds an internal OAuthTransport type and Clerk hooks, routes SignIn/SignUp redirect flows through a transport-aware helper, updates UI components to supply internal callback params and transport-aware behavior, and adds tests and a changeset marking minor bumps.

Changes

OAuth transport implementation across shared, clerk-js, and UI

Layer / File(s) Summary
Shared transport and callback contracts
packages/shared/src/types/oauthTransport.ts, packages/shared/src/types/index.ts, packages/shared/src/types/clerk.ts, packages/shared/src/types/redirects.ts
OAuthTransport type contract added. Clerk types extended with __internal_oauthTransport, __internal_hasOAuthTransport, __internal_handleResourceCallback. HandleOAuthCallbackParams gains optional navigateOnSetActive. AuthenticateWithRedirectParams gains __internal_callbackParams.
Clerk transport state and resource callback entrypoints
packages/clerk-js/src/core/clerk.ts, packages/clerk-js/src/core/__tests__/clerk.test.ts
Clerk stores transport from options (#oauthTransport), exposes getters, adds __internal_handleResourceCallback, and delegates Google One Tap to it. Tests verify initialization and delegation.
navigateOnSetActive routing in redirect callback completion
packages/clerk-js/src/core/clerk.ts, packages/clerk-js/src/core/__tests__/clerk.test.ts
Redirect completion branches now optionally call params.navigateOnSetActive({ session, redirectUrl, decorateUrl }) before falling back to existing navigation flows. Tests added for sign-in/sign-up completion cases.
Transport orchestration utility and resource routing
packages/clerk-js/src/utils/authenticateWithTransport.ts, packages/clerk-js/src/utils/__tests__/authenticateWithTransport.test.ts, packages/clerk-js/src/core/resources/SignIn.ts, packages/clerk-js/src/core/resources/SignUp.ts, packages/clerk-js/src/core/resources/__tests__/SignIn.test.ts, packages/clerk-js/src/core/resources/__tests__/SignUp.test.ts
Adds _authenticateWithTransport to orchestrate external verification URL retrieval, transport.open, optional nonce reload, and final __internal_handleResourceCallback. SignIn/SignUp authenticateWithRedirect delegate to it when transport exists. Tests cover nonce handling, missing verification URL, rejections, and callback dispatch.
UI OAuth callback parameter builders and route prop wiring
packages/ui/src/components/SignIn/buildOAuthCallbackParams.ts, packages/ui/src/components/SignIn/__tests__/buildOAuthCallbackParams.test.ts, packages/ui/src/components/SignIn/index.tsx
Adds buildSignInOAuthCallbackParams and buildSignUpOAuthCallbackParams and refactors SSO callback routes to spread builder output. Tests validate exact shapes and omission of navigateOnSetActive.
Transport-aware social button redirect behavior
packages/ui/src/components/SignIn/SignInSocialButtons.tsx, packages/ui/src/components/SignIn/__tests__/SignInSocialButtons.test.tsx, packages/ui/src/components/SignUp/SignUpSocialButtons.tsx, packages/ui/src/components/SignUp/__tests__/SignUpSocialButtons.test.tsx
Social buttons now pass __internal_callbackParams (including navigateOnSetActive) to redirect flows, adjust popup/idle logic when a transport exists, and call card.setIdle() on transport-path errors. Component tests added.
UserProfile connected account transport flow
packages/ui/src/components/UserProfile/ConnectedAccountsMenu.tsx, packages/ui/src/components/UserProfile/ConnectedAccountsSection.tsx, packages/ui/src/components/UserProfile/__tests__/ConnectedAccountsSection.test.tsx
Connected account create/reconnect flows accept explicit redirect URLs, use transport.getRedirectUrl()/open when available, decorate modal state, reload user after transport completion, and update tests to assert redirect modal state and transport usage.
Changeset release note and bumps
.changeset/oauth-transport.md
Marks @clerk/shared, @clerk/clerk-js, and @clerk/ui as minor; documents the internal OAuth transport for native wrappers and clarifies no change when no transport is registered.

Sequence Diagram(s)

sequenceDiagram
  participant Resource as SignIn/SignUp Resource
  participant AuthUtil as _authenticateWithTransport
  participant OAuthTransport
  participant Clerk

  Resource->>AuthUtil: authenticateWithRedirect(params)
  AuthUtil->>OAuthTransport: getRedirectUrl()
  AuthUtil->>Resource: authenticateMethod(..., { redirectUrl })
  Resource-->>AuthUtil: verificationUrl
  AuthUtil->>OAuthTransport: open(verificationUrl)
  OAuthTransport-->>AuthUtil: { callbackUrl }
  AuthUtil->>Resource: reload({ rotatingTokenNonce? })
  AuthUtil->>Clerk: __internal_handleResourceCallback(resource, callbackParams)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested reviewers

  • austincalvelage
  • Ephem

"🐰 I fetched the redirect, opened the gate,
A nonce, a callback — what a fate!
Through native brows' bright chrome-lit arc,
I hop the user safely back to the park. 🥕"

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and specifically describes the main change: adding OAuth transport support for external auth flows across clerk-js and ui packages.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

github-actions Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

API Changes Report

Generated by Break Check on 2026-06-12T03:13:50.676Z

Summary

Metric Count
Packages analyzed 19
Packages with changes 3
🔴 Breaking changes 0
🟡 Non-breaking changes 4
🟢 Additions 6

🤖 This report was reviewed by claude-sonnet-4-6.

Note
Break Check could not snapshot 1 subpath; the diff below excludes them.

  • @clerk/astro ./env: Internal Error: Unable to determine module for: /home/runner/_work/javascript/javascript/packages/astro/env.d.ts You have encountered a software defect. Please consider reporting the issue to the maintainers of this application.

@clerk/clerk-js

Current version: 6.16.1
Recommended bump: MINOR → 6.17.0

Subpath .

🟢 Additions (3)

Added: Clerk.__internal_handleResourceCallback
+ __internal_handleResourceCallback: (signInOrUp: SignInResource | SignUpResource, params: HandleOAuthCallbackParams, customNavigate?: (to: string) => Promise<unknown>) => Promise<unknown>;

Added property Clerk.__internal_handleResourceCallback

Added: Clerk.__internal_hasOAuthTransport
+ get __internal_hasOAuthTransport(): boolean;

Added property Clerk.__internal_hasOAuthTransport

Added: Clerk.__internal_oauthTransport
+ get __internal_oauthTransport(): OAuthTransport | null;

Added property Clerk.__internal_oauthTransport

Subpath ./no-rhc

🟢 Additions (3)

Added: Clerk.__internal_handleResourceCallback
+ __internal_handleResourceCallback: (signInOrUp: SignInResource | SignUpResource, params: HandleOAuthCallbackParams, customNavigate?: (to: string) => Promise<unknown>) => Promise<unknown>;

Added property Clerk.__internal_handleResourceCallback

Added: Clerk.__internal_hasOAuthTransport
+ get __internal_hasOAuthTransport(): boolean;

Added property Clerk.__internal_hasOAuthTransport

Added: Clerk.__internal_oauthTransport
+ get __internal_oauthTransport(): OAuthTransport | null;

Added property Clerk.__internal_oauthTransport


@clerk/shared

Current version: 4.17.1
Recommended bump: MINOR → 4.18.0

Subpath ./types

🟡 Non-breaking Changes (3)

Modified: AuthenticateWithRedirectParams
// ... 7 unchanged lines elided ...
    emailAddress?: string;
    legalAccepted?: boolean;
    oidcPrompt?: string;
+   __internal_callbackParams?: HandleOAuthCallbackParams;
    enterpriseConnectionId?: string;
  };

Static analyzer: Breaking change in type alias AuthenticateWithRedirectParams: Type changed: {redirectUrl:string;redirectUrlComplete:string;continueSignUp?:boolean;continueSignIn?:boolean;strategy:import("@clerk/…{redirectUrl:string;redirectUrlComplete:string;continueSignUp?:boolean;continueSignIn?:boolean;strategy:import("@clerk/…

🤖 AI review (reclassified as non-breaking) (95%): A new optional property __internal_callbackParams?: HandleOAuthCallbackParams was added to AuthenticateWithRedirectParams, which is used only as a function parameter (input position); adding an optional property to an input type is non-breaking for existing callers who do not supply it.

Modified: ClerkOptions
// ... 30 unchanged lines elided ...
    __internal_keyless_claimKeylessApplicationUrl?: string;
    __internal_keyless_copyInstanceKeysUrl?: string;
    __internal_keyless_dismissPrompt?: (() => Promise<void>) | null;
+   __internal_oauthTransport?: OAuthTransport;
    taskUrls?: Partial<Record<SessionTask['key'], string>>;
  };

Static analyzer: Breaking change in type alias ClerkOptions: Type changed: import("@clerk/shared").AfterMultiSessionSingleSignOutUrl&import("@clerk/shared").AfterSignOutUrl&import("@clerk/shared…import("@clerk/shared").AfterMultiSessionSingleSignOutUrl&import("@clerk/shared").AfterSignOutUrl&import("@clerk/shared…

🤖 AI review (reclassified as non-breaking) (95%): A new optional property __internal_oauthTransport?: OAuthTransport was added to ClerkOptions, which is used as a function parameter/input type; adding an optional property to an input type does not break existing callers.

Modified: HandleOAuthCallbackParams
// ... 8 unchanged lines elided ...
    verifyPhoneNumberUrl?: string | null;
    reloadResource?: 'signIn' | 'signUp';
    unsafeMetadata?: SignUpUnsafeMetadata;
+   navigateOnSetActive?: (opts: {
+     session: SessionResource;
+     redirectUrl: string;
+     decorateUrl: (url: string) => string;
+   }) => Promise<unknown>;
  };

Static analyzer: Breaking change in type alias HandleOAuthCallbackParams: Type changed: import("@clerk/shared").SignInFallbackRedirectUrl&import("@clerk/shared").SignInForceRedirectUrl&import("@clerk/shared"…import("@clerk/shared").SignInFallbackRedirectUrl&import("@clerk/shared").SignInForceRedirectUrl&import("@clerk/shared"…

🤖 AI review (reclassified as non-breaking) (95%): A new optional property navigateOnSetActive? was added to HandleOAuthCallbackParams, which is used only in input/parameter positions (handleRedirectCallback, handleGoogleOneTapCallback); adding an optional property to an input type is non-breaking for existing callers.


@clerk/vue

Current version: 2.4.2
Recommended bump: MINOR → 2.5.0

🟡 Non-breaking Changes (1)

Modified: AuthenticateWithRedirectCallback

// ... 8 unchanged lines elided ...
      verifyPhoneNumberUrl?: string | null;
      reloadResource?: "signIn" | "signUp";
      unsafeMetadata?: SignUpUnsafeMetadata;
+     navigateOnSetActive?: (opts: {
+         session: import("@clerk/shared/types").SessionResource;
+         redirectUrl: string;
+         decorateUrl: (url: string) => string;
+     }) => Promise<unknown>;
  } & {}, import("vue").PublicProps>

Static analyzer: Breaking change in variable AuthenticateWithRedirectCallback: Type changed: import("@vue/runtime-core").DefineSetupFnComponent<import("@clerk/shared").HandleOAuthCallbackParams,{},{},import("@cle…import("@vue/runtime-core").DefineSetupFnComponent<import("@clerk/shared").HandleOAuthCallbackParams,{},{},import("@cle…

🤖 AI review (reclassified as non-breaking) (90%): A new optional property navigateOnSetActive? is added to the component's props type; existing consumers who do not pass this prop are unaffected, and since it is optional no existing call site needs updating.


Report generated by Break Check

Last ran on 871776e.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/ui/src/components/UserProfile/ConnectedAccountsSection.tsx (1)

132-155: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't fall through to navigate('') when reconnect returns no redirect URL.

response.verification?.externalVerificationRedirectURL is optional here, but the web fallback coerces the missing case into navigate(''), and the transport branch just returns. That turns an incomplete reconnect response into a broken/no-op flow instead of an actionable error. Please guard the URL first and send the missing-data case through handleError.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ui/src/components/UserProfile/ConnectedAccountsSection.tsx` around
lines 132 - 155, The web fallback currently calls navigate('') when
response.verification?.externalVerificationRedirectURL is missing; update the
logic in ConnectedAccountsSection.tsx after the createExternalAccount /
account.reauthorize call to first check that
response?.verification?.externalVerificationRedirectURL (or its href) exists and
is non-empty, and only call navigate(...) when it does; if it is missing, call
handleError(...) with an appropriate error/context (same error path you expect
for incomplete reconnects) and do not fall through to navigate; mirror the
transport branch behavior (which returns early) by returning after handling the
error or successful open so you don't continue into navigate on missing data.
🧹 Nitpick comments (2)
packages/ui/src/components/UserProfile/__tests__/ConnectedAccountsSection.test.tsx (1)

140-177: ⚡ Quick win

Add a transport reconnect test for the new ConnectedAccount.reconnect path.

The added transport coverage only exercises the add-connection menu. The reconnect flow now has separate transport behavior (getRedirectUrl, reauthorize vs createExternalAccount, open, user.reload), but none of that is asserted in this suite yet.

As per coding guidelines, **/*.{test,spec}.{ts,tsx}: Unit tests are required for all new functionality.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/ui/src/components/UserProfile/__tests__/ConnectedAccountsSection.test.tsx`
around lines 140 - 177, Add a unit test in ConnectedAccountsSection.test.tsx
that covers the new reconnect path on ConnectedAccount.reconnect: set up a
fixture with an existing external account, register a mock
__internal_oauthTransport that implements getRedirectUrl and reauthorize
(mockResolvedValue), render the component and trigger theReconnect UI action
(click the reconnect button), then assert that
transport.getRedirectUrl/reauthorize were called with the expected URL/params,
that user.reload was called, and that the add-connection flow helpers
(createExternalAccount/open) were not invoked for this reconnect path; reference
the test helpers/objects fixtures.clerk,
fixtures.clerk.__internal_oauthTransport, ConnectedAccount.reconnect, and
fixtures.clerk.user!.reload when implementing the assertions.

Source: Coding guidelines

packages/clerk-js/src/core/resources/__tests__/SignUp.test.ts (1)

38-79: ⚡ Quick win

Consider additional test coverage for transport behavior.

The test validates the happy path but could be more comprehensive:

  1. Missing assertion: The test name claims the transport routes "instead of windowNavigate," but there's no assertion verifying that windowNavigate (or similar browser redirect mechanisms) is NOT called. Consider adding a spy to verify the normal redirect path is bypassed.

  2. Error handling: No coverage for what happens when transport.open rejects or when handleResourceCallback fails. These paths should be tested to ensure proper error propagation.

  3. Backwards compatibility: Consider adding a test case where no transport is registered to verify the existing redirect behavior still works (regression protection).

📋 Suggested additional test cases
it('falls back to normal redirect when no transport is registered', async () => {
  const mockNavigate = vi.fn();
  SignUp.clerk = {
    buildUrlWithAuth: vi.fn(u => u),
    // No __internal_oauthTransport
    __internal_environment: { displayConfig: { captchaOauthBypass: [] } },
  } as any;
  
  // Mock windowNavigate or similar
  const navigateSpy = vi.spyOn(window, 'location', 'set');
  
  const mockFetch = vi.fn().mockResolvedValueOnce({
    client: null,
    response: {
      id: 'signup_123',
      verifications: {
        external_account: {
          status: 'unverified',
          external_verification_redirect_url: 'https://provider.example/auth',
        },
      },
    },
  });
  BaseResource._fetch = mockFetch;
  
  const signUp = new SignUp();
  await signUp.authenticateWithRedirect({
    strategy: 'oauth_google',
    redirectUrl: '/sso-callback',
    redirectUrlComplete: '/',
  } as any);
  
  // Verify normal redirect occurred
  expect(navigateSpy).toHaveBeenCalled();
});

it('handles transport rejection gracefully', async () => {
  const open = vi.fn().mockRejectedValue(new Error('Transport unavailable'));
  SignUp.clerk = {
    buildUrlWithAuth: vi.fn(u => u),
    __internal_oauthTransport: { getRedirectUrl: () => 'myapp://sso-callback', open },
    __internal_handleResourceCallback: vi.fn(),
    __internal_environment: { displayConfig: { captchaOauthBypass: [] } },
  } as any;
  
  const mockFetch = vi.fn().mockResolvedValueOnce({
    client: null,
    response: {
      id: 'signup_123',
      verifications: {
        external_account: {
          status: 'unverified',
          external_verification_redirect_url: 'https://provider.example/auth',
        },
      },
    },
  });
  BaseResource._fetch = mockFetch;
  
  const signUp = new SignUp();
  
  await expect(
    signUp.authenticateWithRedirect({
      strategy: 'oauth_google',
      redirectUrl: '/sso-callback',
      redirectUrlComplete: '/',
      __internal_callbackParams: { signInUrl: '/sign-in' },
    } as any)
  ).rejects.toThrow('Transport unavailable');
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/clerk-js/src/core/resources/__tests__/SignUp.test.ts` around lines
38 - 79, Test currently only asserts the happy path through the OAuth transport
(SignUp.authenticateWithRedirect using
SignUp.clerk.__internal_oauthTransport.open) but misses verifying the normal
browser redirect is not invoked and lacks failure/back-compat cases; add three
tests: (1) spy on the browser navigation mechanism (e.g., spyOn(window,
'location', 'set') or the project's windowNavigate helper) and assert it is NOT
called when __internal_oauthTransport is present, (2) a test where
__internal_oauthTransport.open rejects and assert authenticateWithRedirect
rejects with that error (and that __internal_handleResourceCallback is not
called), and (3) a fallback test with no __internal_oauthTransport registered
that asserts the code falls back to the normal redirect path (using
BaseResource._fetch to return the external_verification_redirect_url and
verifying the window navigation spy was called).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/ui/src/components/UserProfile/ConnectedAccountsMenu.tsx`:
- Around line 48-64: Both branches call createExternalAccount but only proceed
when res.verification?.externalVerificationRedirectURL exists; instead of
silently returning (transport branch) or doing nothing (web branch) when that
URL is missing, explicitly validate and surface the failure so the existing
catch/error path handles it: after calling createExternalAccount (in both the
clerk.__internal_oauthTransport branch and the window.location.href branch), if
the URL is falsy throw a descriptive Error('Missing
externalVerificationRedirectURL') (or invoke the shared error handler if one
exists) rather than returning or continuing, so the outer try/catch will route
the failure consistently; keep the existing successful flows that call
clerk.__internal_oauthTransport.open, user.reload, card.setIdle(strategy),
sleep, and navigate unchanged.

---

Outside diff comments:
In `@packages/ui/src/components/UserProfile/ConnectedAccountsSection.tsx`:
- Around line 132-155: The web fallback currently calls navigate('') when
response.verification?.externalVerificationRedirectURL is missing; update the
logic in ConnectedAccountsSection.tsx after the createExternalAccount /
account.reauthorize call to first check that
response?.verification?.externalVerificationRedirectURL (or its href) exists and
is non-empty, and only call navigate(...) when it does; if it is missing, call
handleError(...) with an appropriate error/context (same error path you expect
for incomplete reconnects) and do not fall through to navigate; mirror the
transport branch behavior (which returns early) by returning after handling the
error or successful open so you don't continue into navigate on missing data.

---

Nitpick comments:
In `@packages/clerk-js/src/core/resources/__tests__/SignUp.test.ts`:
- Around line 38-79: Test currently only asserts the happy path through the
OAuth transport (SignUp.authenticateWithRedirect using
SignUp.clerk.__internal_oauthTransport.open) but misses verifying the normal
browser redirect is not invoked and lacks failure/back-compat cases; add three
tests: (1) spy on the browser navigation mechanism (e.g., spyOn(window,
'location', 'set') or the project's windowNavigate helper) and assert it is NOT
called when __internal_oauthTransport is present, (2) a test where
__internal_oauthTransport.open rejects and assert authenticateWithRedirect
rejects with that error (and that __internal_handleResourceCallback is not
called), and (3) a fallback test with no __internal_oauthTransport registered
that asserts the code falls back to the normal redirect path (using
BaseResource._fetch to return the external_verification_redirect_url and
verifying the window navigation spy was called).

In
`@packages/ui/src/components/UserProfile/__tests__/ConnectedAccountsSection.test.tsx`:
- Around line 140-177: Add a unit test in ConnectedAccountsSection.test.tsx that
covers the new reconnect path on ConnectedAccount.reconnect: set up a fixture
with an existing external account, register a mock __internal_oauthTransport
that implements getRedirectUrl and reauthorize (mockResolvedValue), render the
component and trigger theReconnect UI action (click the reconnect button), then
assert that transport.getRedirectUrl/reauthorize were called with the expected
URL/params, that user.reload was called, and that the add-connection flow
helpers (createExternalAccount/open) were not invoked for this reconnect path;
reference the test helpers/objects fixtures.clerk,
fixtures.clerk.__internal_oauthTransport, ConnectedAccount.reconnect, and
fixtures.clerk.user!.reload when implementing the assertions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Repository UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: 34b8dd7e-d1ba-47ad-933e-29e071fb8609

📥 Commits

Reviewing files that changed from the base of the PR and between d0ed42f and 78d0941.

📒 Files selected for processing (23)
  • .changeset/oauth-transport.md
  • packages/clerk-js/src/core/__tests__/clerk.test.ts
  • packages/clerk-js/src/core/clerk.ts
  • packages/clerk-js/src/core/resources/SignIn.ts
  • packages/clerk-js/src/core/resources/SignUp.ts
  • packages/clerk-js/src/core/resources/__tests__/SignIn.test.ts
  • packages/clerk-js/src/core/resources/__tests__/SignUp.test.ts
  • packages/clerk-js/src/utils/__tests__/authenticateWithTransport.test.ts
  • packages/clerk-js/src/utils/authenticateWithTransport.ts
  • packages/shared/src/types/clerk.ts
  • packages/shared/src/types/index.ts
  • packages/shared/src/types/oauthTransport.ts
  • packages/shared/src/types/redirects.ts
  • packages/ui/src/components/SignIn/SignInSocialButtons.tsx
  • packages/ui/src/components/SignIn/__tests__/SignInSocialButtons.test.tsx
  • packages/ui/src/components/SignIn/__tests__/buildOAuthCallbackParams.test.ts
  • packages/ui/src/components/SignIn/buildOAuthCallbackParams.ts
  • packages/ui/src/components/SignIn/index.tsx
  • packages/ui/src/components/SignUp/SignUpSocialButtons.tsx
  • packages/ui/src/components/SignUp/__tests__/SignUpSocialButtons.test.tsx
  • packages/ui/src/components/UserProfile/ConnectedAccountsMenu.tsx
  • packages/ui/src/components/UserProfile/ConnectedAccountsSection.tsx
  • packages/ui/src/components/UserProfile/__tests__/ConnectedAccountsSection.test.tsx

Comment on lines +48 to +64
try {
if (clerk.__internal_oauthTransport) {
const res = await createExternalAccount(String(await clerk.__internal_oauthTransport.getRedirectUrl()));
const url = res?.verification?.externalVerificationRedirectURL;
if (url) {
await clerk.__internal_oauthTransport.open(url);
await user.reload();
}
})
.catch(err => {
handleError(err, [], card.setError);
card.setIdle(strategy);
});
void sleep(2000).then(() => card.setIdle(strategy));
return;
}

const res = await createExternalAccount(window.location.href);
if (res && res.verification?.externalVerificationRedirectURL) {
void sleep(2000).then(() => card.setIdle(strategy));
void navigate(res.verification.externalVerificationRedirectURL.href);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle the missing verification URL before treating connect as success.

This branch only transitions out of loading when res.verification?.externalVerificationRedirectURL exists. If createExternalAccount() resolves without that URL, the web path leaves the action stuck loading, while the transport path quietly returns after the idle timer. Please validate the URL explicitly and route the failure through the existing error path instead of letting the flow no-op.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ui/src/components/UserProfile/ConnectedAccountsMenu.tsx` around
lines 48 - 64, Both branches call createExternalAccount but only proceed when
res.verification?.externalVerificationRedirectURL exists; instead of silently
returning (transport branch) or doing nothing (web branch) when that URL is
missing, explicitly validate and surface the failure so the existing catch/error
path handles it: after calling createExternalAccount (in both the
clerk.__internal_oauthTransport branch and the window.location.href branch), if
the URL is falsy throw a descriptive Error('Missing
externalVerificationRedirectURL') (or invoke the shared error handler if one
exists) rather than returning or continuing, so the outer try/catch will route
the failure consistently; keep the existing successful flows that call
clerk.__internal_oauthTransport.open, user.reload, card.setIdle(strategy),
sleep, and navigate unchanged.

@pkg-pr-new

pkg-pr-new Bot commented Jun 11, 2026

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

npm i https://pkg.pr.new/@clerk/astro@8831

@clerk/backend

npm i https://pkg.pr.new/@clerk/backend@8831

@clerk/chrome-extension

npm i https://pkg.pr.new/@clerk/chrome-extension@8831

@clerk/clerk-js

npm i https://pkg.pr.new/@clerk/clerk-js@8831

@clerk/expo

npm i https://pkg.pr.new/@clerk/expo@8831

@clerk/expo-passkeys

npm i https://pkg.pr.new/@clerk/expo-passkeys@8831

@clerk/express

npm i https://pkg.pr.new/@clerk/express@8831

@clerk/fastify

npm i https://pkg.pr.new/@clerk/fastify@8831

@clerk/hono

npm i https://pkg.pr.new/@clerk/hono@8831

@clerk/localizations

npm i https://pkg.pr.new/@clerk/localizations@8831

@clerk/nextjs

npm i https://pkg.pr.new/@clerk/nextjs@8831

@clerk/nuxt

npm i https://pkg.pr.new/@clerk/nuxt@8831

@clerk/react

npm i https://pkg.pr.new/@clerk/react@8831

@clerk/react-router

npm i https://pkg.pr.new/@clerk/react-router@8831

@clerk/shared

npm i https://pkg.pr.new/@clerk/shared@8831

@clerk/tanstack-react-start

npm i https://pkg.pr.new/@clerk/tanstack-react-start@8831

@clerk/testing

npm i https://pkg.pr.new/@clerk/testing@8831

@clerk/ui

npm i https://pkg.pr.new/@clerk/ui@8831

@clerk/upgrade

npm i https://pkg.pr.new/@clerk/upgrade@8831

@clerk/vue

npm i https://pkg.pr.new/@clerk/vue@8831

commit: 871776e

Comment on lines +383 to +393
const transport = SignIn.clerk.__internal_oauthTransport;
if (transport) {
return _authenticateWithTransport({
clerk: SignIn.clerk,
transport,
resource: this,
authenticateMethod: this.authenticateWithRedirectOrPopup,
params,
callbackParams: params.__internal_callbackParams ?? {},
});
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If no transport is registered, the existing redirect behavior is preserved. If a transport is registered, we reuse the same private authenticateWithRedirectOrPopup initiation path through _authenticateWithTransport

* Exact callback params the SignIn `sso-callback` route passes to SSOCallback.
* Excludes `navigateOnSetActive`, which is transport-only.
*/
export function buildSignInOAuthCallbackParams(ctx: SignInContextType): HandleOAuthCallbackParams {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mirror sthe props passed by the normal sso-callback routes. . In Electron, the renderer does not navigate to that route, so the social button passes the same callback context up front before the external transport opens

const redirectUrl = ctx.ssoCallbackUrl;
const redirectUrlComplete = ctx.afterSignInUrl || '/';
const shouldUsePopup = ctx.oauthFlow === 'popup' || (ctx.oauthFlow === 'auto' && originPrefersPopup());
const shouldUsePopup =

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when an OAuth transport is registered, it takes precedence over oauthFlow. oauthFlow chooses between browser redirect and browser popup. Electron supplies a runtime transport instead, so popup/redirect should not be selected inside the renderer.

*
* @internal
*/
export async function _authenticateWithTransport(opts: {

@wobsoriano wobsoriano Jun 11, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is the core seam for Electron. It does not reimplement signIn.create, signUp.create, captcha retry, or other OAuth initiation logic. It calls the existing resource authenticateWithRedirectOrPopup method and only replaces the navigation callback so we can capture the provider verification URL and hand it to an external transport

The electron side of this is in #8835, where preload/main provide the transport implementation

const redirectUrl = isModal ? appendModalState({ url: window.location.href, componentName }) : window.location.href;

try {
const redirectUrl = clerk.__internal_oauthTransport

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

connected accounts do not go through authenticateWithRedirectOrPopup, so this component needs a small local transport branch. The branch only swaps the callback URL and external URL opening

@wobsoriano wobsoriano requested a review from a team June 11, 2026 18:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant